#include "HBSD.h"
#include <stdio.h>
#include <exception>
#include <stdlib.h>
#include <fstream>
#include "HBSD_Routing.h"
#include "HBSD_SAX.h"
#include <netinet/in.h>
#include <netdb.h>
#include "Util.h"
#include "Console_Logging.h"
#include "Requester.h"
#include <iostream>
#include "ConfigFile.h"
#include <xercesc/sax2/XMLReaderFactory.hpp>
#include <xercesc/sax2/DefaultHandler.hpp>
#include <xercesc/util/XMLString.hpp>

using namespace std;

string HBSD::routerEndpoint;
string HBSD::loggingClass;
	
// "Global" values
Logging *HBSD::log;
ConfigFile * HBSD::routerConf;
string HBSD::localEID;
Requester* HBSD::requester;
string HBSD::hbsdRegistration;

int HBSD::dtndSocket;
struct sockaddr_in HBSD::dtndSocketAddr;

// Command line options
string HBSD::confFile;
bool HBSD::xmlSchemaOpt;
bool HBSD::logLevelOpt;
string HBSD::hbsdVersion;
string HBSD::defaultLoggingClass;

// Configurable values
string HBSD::multicastGroup;
int HBSD::multicastPort;
string HBSD::xmlSchema;
string HBSD::logConfiguration;
int HBSD::logLevel;
bool HBSD::xmlValidate;
HBSD_Routing *HBSD::handlerHBSD;
HBSD_SAX* HBSD::saxHandler;
SAX2XMLReader* HBSD::saxReader;
string HBSD::LOOPBACKADR;
bool HBSD::staticInitialized;

HBSD::HBSD(int thrd) 
{
	if(!staticInitialized)
	{
		xmlSchemaOpt = false;
		logLevelOpt = false;
		hbsdVersion.assign("0.0.1");
		defaultLoggingClass.assign("Console_Logging");
		multicastPort = 8001;
		// Configurable values
		multicastGroup.assign("224.0.0.2");
		xmlValidate = false;
		handlerHBSD = NULL;
		saxHandler = NULL;
		saxReader = NULL;
		LOOPBACKADR.assign("127.0.0.1");
		requester = NULL;
		log = NULL;
		routerConf = NULL;
		routerEndpoint.append("ext.rtr/HBSD");
		loggingClass = defaultLoggingClass;
	}

	switch (thrd) 
	{
		case THRD_ROUTERLOOP:
			startRouterLoop();
			break;
		default:
			log->error(string("Attempt to start unkown thread: ") + Util::to_string(thrd));
			break;
	}
}

void HBSD::InitStaticMembers()
{
	xmlSchemaOpt = false;
	logLevelOpt = false;
	hbsdVersion.assign("0.0.1");
	defaultLoggingClass.assign("Console_Logging");
	multicastPort = 8001;
	// Configurable values
	multicastGroup.assign("224.0.0.2");
	xmlValidate = false;
	handlerHBSD = NULL;
	saxHandler = NULL;
	saxReader = NULL;
	LOOPBACKADR.assign("127.0.0.1");
	requester = NULL;
	log = NULL;
	routerConf = NULL;
	routerEndpoint.append("ext.rtr/HBSD");
	loggingClass = defaultLoggingClass;
	staticInitialized = true;
}

HBSD::~HBSD()
{
	if(handlerHBSD != NULL)
		delete handlerHBSD;
	if(saxHandler != NULL)
		delete saxHandler;
	if(routerConf != NULL)
		delete routerConf;
	if(requester != NULL)
		delete requester;
}


void HBSD::parseOpts(list<string>& args) 
{
	list<string>::iterator iter = args.begin();
	while (iter != args.end()) 
	{
		string arg = *iter;
		try 
		{
			if (arg.compare("-config_file") == 0)
			{
				iter++;
				confFile = *iter;
			} else if (arg.compare("-xml") == 0) 
			{
				iter++;
				xmlSchema = *iter;
				xmlSchemaOpt = true;
			} else if (arg.compare("-log_file") == 0) 
			{
				iter++;
				logConfiguration = *iter;
			} else if (arg.compare("-log_level") == 0) 
			{
				iter++;
				logLevel = atoi((*(iter)).c_str());
				logLevelOpt = true;
			} else if (arg.compare("-version") == 0) 
			{
				versionExit();
			} else if (arg.compare("-help") == 0) 
			{
				helpExit();
			} else 
			{
				cerr<<"Unknown option: "<< arg.c_str()<<endl;
				exit(1);
			}
		
		} 
		catch (exception &e) 
		{
			cerr<< "Missing parameter after option: "<< arg.c_str();
			exit(1);
		}
		iter++;
	}
}

void HBSD::helpExit() 
{
	fprintf(stdout,"HBSD [-config_file] [-xml] [-log_file] [-level]\n");
	fprintf(stdout,"   -config_file   Specifies the HBSD configuration file to be used.\n");
	fprintf(stdout,"   -xml           Router XML schema file. Default is /etc/router.xsd.\n");
	fprintf(stdout,"   -log_file      Logging configuration file.\n");
	fprintf(stdout,"   -log_level     Logging level (0-6).\n");
	fprintf(stdout,"   -version       Print the HBSD version number and exit.\n");
	fprintf(stdout,"   -help          Print this help text and exit.\n"); 

	exit(0);
}


void HBSD::versionExit() 
{
	cout<<"HBSD Version: "<<hbsdVersion<<endl;
	exit(0);
}


void HBSD::loadConfig() 
{
	if (!confFile.empty()) 
	{
		cout<<"Loading conffile: "<<confFile<<endl;
		routerConf = new ConfigFile(confFile);
		if (routerConf->parse())
		{
			loggingClass = routerConf->getstring(string("loggingClass"), defaultLoggingClass);
			cout<<"Logging class: "<<loggingClass<<endl;
			routerEndpoint = routerConf->getstring(string("routerEndpoint"), routerEndpoint);
			cout<<"Router endPoint: "<<routerEndpoint<<endl;
			multicastGroup = routerConf->getstring(string("multicastGroup"), multicastGroup);
			cout<<"multicastGroup: "<<multicastGroup<<endl;
			multicastPort = routerConf->getInt(string("multicastPort"), multicastPort);
			cout<<"multicastPort: "<<multicastPort<<endl;
			xmlValidate = routerConf->getBoolean(string("xmlValidate"), xmlValidate);
			cout <<"xmlValidate: "<<xmlValidate<<endl;
			if (!xmlSchemaOpt)
			{
				xmlSchema = routerConf->getstring(string("xmlSchema"), xmlSchema);
				cout<<"xmlSchema: "<<xmlSchema<<endl;
			}
			if (!logConfiguration.empty()) 
			{
				logConfiguration = routerConf->getstring(string("logConfiguration"), string(""));
				cout<<"logConfiguration: "<<logConfiguration<<endl;
			}
			if (!logLevelOpt) 
			{
				if (routerConf->exists(string("logLevel"))) 
				{
					logLevel = routerConf->getInt(string("logLevel"), Logging::WARN);
					logLevelOpt = true;
					cout<<"logLevel: "<<logLevel<<endl;
				}
			}
		} else 
		{
			cerr<<"Error in routerConf->parse(), "<<__FILE__<<" : "<<__LINE__<<endl;
			exit(1);
		}
	} else 
	{
		// "Empty" configuration file.
		routerConf = new ConfigFile();
	}
}

void HBSD::setupSAX()
{
	// If we're doing validation, make sure the xsd file is there.
	ifstream xmlSch;
	xmlSch.open(xmlSchema.c_str(), ifstream::out);

	if (xmlValidate && !xmlSch.is_open()) 
	{
		cerr<<"XML schema file does not exist or cannot be read: "<< xmlSchema<<endl;
		exit(1);
	}
	
		
	// Set up the SAX parser/reader and associate it with our handler.
	handlerHBSD = new HBSD_Routing();
	saxHandler = new HBSD_SAX(handlerHBSD);

	try 
	{	
		XMLPlatformUtils::Initialize();

		saxReader = XMLReaderFactory::createXMLReader();
		saxReader->setErrorHandler(saxHandler);
		saxReader->setContentHandler(saxHandler);
	} 
	catch(const XMLException & toCatch)
	{
		char * message = XMLString::transcode(toCatch.getMessage());
		cout <<"Error During Initialization ! :"<<endl;
		cout << message<<endl;
		XMLString::release(&message);
		exit(1);
	}
	/*catch (ParserConfigurationException &e) 
	{
		cerr<<"SAX parser does not support requested configuration"<<endl;
		exit(1);
	}*/ 
	catch (SAXException  &e) 
	{
		cerr<<"Unanticpated SAX parser exception"<<endl;
	} 
	catch (exception &e) 
	{
		cerr<<"Unanticipated exception"<<endl;
		exit(1);
	}

	// If validation is enabled, set it up.

	if (xmlValidate) 
	{
		try 
		{

			saxReader->setFeature(XMLUni::fgSAX2CoreValidation,true);
			saxReader->setFeature(XMLUni::fgXercesSchema,true);
			saxReader->setFeature(XMLUni::fgXercesSchemaFullChecking,true);
			saxReader->setProperty(XMLUni::fgXercesSchemaExternalNoNameSpaceSchemaLocation,(void*)xmlSchema.c_str());
		} 
		catch (exception &e)
		{
			cerr<<"Unable to configure XML validation"<<endl;
			exit(1);
		}
	}
}
	
void HBSD::setupMulticast() 
{
	try 
	{
		dtndSocket = socket(AF_INET, SOCK_DGRAM, 0);
		if(dtndSocket <0)
		{		
			cerr<<"Multicast socket creation error"<<endl;
			perror ("The following error occurred");
			exit(1);
		}

		// Init the socket
		memset(&dtndSocketAddr, 0, sizeof(dtndSocketAddr));
		dtndSocketAddr.sin_family = AF_INET;
		dtndSocketAddr.sin_port = htons(multicastPort);
		dtndSocketAddr.sin_addr.s_addr = inet_addr(multicastGroup.c_str());
	
		
		struct ip_mreq imr;
		imr.imr_multiaddr.s_addr = inet_addr(multicastGroup.c_str());
		imr.imr_interface.s_addr = inet_addr(LOOPBACKADR.c_str());
		// Join the Multicast Group

		if (setsockopt(dtndSocket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &imr, sizeof(struct ip_mreq)) < 0) 
		{
        		cerr<< "setsockopt - IP_ADD_MEMBERSHIP"<<endl;
			perror ("The following error occurred");
        		exit(1);
    		}
		// Disaable the Multicast LOOP
		unsigned char loop = 0;
		if(setsockopt(dtndSocket, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof(loop)) < 0)
		{
			cerr << "setsockopt - IP_MULTICAST_LOOP"<<endl;
			perror ("The following error occurred");
			exit(1);
		}

		//Bind the Multicast socket for reception
		if (bind(dtndSocket, (sockaddr*)&dtndSocketAddr, sizeof(dtndSocketAddr)) < 0) 
		{
         		cerr<<"Multicast socket binding error"<<endl;
			perror ("The following error occurred");
         		exit(1);
     		}

		requester = new Requester();
		requester->init(multicastGroup, multicastPort);
	} 
	catch (exception &e) 
	{
		cerr<<"Unable to setup sockets: "<<e.what()<<endl;
		exit(1);
	}
}

void HBSD::startRouterLoop()
{
	log->info(string("HBSD external DTN router version: ") + hbsdVersion);

	log->info(string("HBSD router started and listening on port ") + Util::to_string(multicastPort));
	
	char msg[MAX_DTNDXML_SZ];
	handlerHBSD->initialized();
	while (true) 
	{
		try 
		{
			// Wait for a packet on the loopback multicast socket.
			int sizeAddr = sizeof(dtndSocketAddr);	
			int cnt = recvfrom(dtndSocket, msg, sizeof(msg), 0, (sockaddr*)&dtndSocketAddr, (socklen_t*)&sizeAddr);

			if (log->enabled(Logging::DEBUG)) 
			{
				log->debug(string("Packet received"));
			}
			try 
			{
				string s;
				s.assign(msg);
				remove(s.begin(), s.end(), ' ');
				log->trace(s);
				saxReader->parse(s.c_str());
			} 
			catch (SAXException &e) 
			{
				if (log->enabled(Logging::ERROR)) 
				{
					log->error(string("Error parsing XML packet: ") + string(XMLString::transcode(e.getMessage())));
				}
				continue;
			} 
			catch (exception &e) 
			{
				if (log->enabled(Logging::ERROR)) 
				{
					log->error(string("Unanticipated XML parsing error: ") + string(e.what()));
				}
				continue;
			}
			
		} 
		catch (exception &e) 
		{
			log->fatal(string("Unanticipated exception in multicast receive loop"));
			break;
		}
	}
}
